#include "GenerateICode.h"
#include "GenerateX86ICode.h"
#include <stdio.h>

static const char* instructionTypeStrings[] =
{
	"IT_ASSIGN_VAR_TO_VAR",
	"IT_ASSIGN_CONSTANT_TO_VAR",

	"IT_ADD_VAR_TO_VAR",
	"IT_ADD_CONSTANT_TO_VAR",

	"IT_SUB_VAR_FROM_VAR",
	"IT_SUB_CONSTANT_FROM_VAR",

	"IT_MUL_VAR_BY_VAR",
	"IT_MUL_VAR_BY_CONSTANT",

	"IT_DIV_VAR_BY_VAR",
	"IT_DIV_VAR_BY_CONSTANT",

	"IT_JMP",

	"IT_RETURN"
};

// icode -> x86icode
void GenerateX86ICode()
{
	struct Instruction* i = (struct Instruction*)icodeData;
	Int stackOffset = 0;
	//unsigned int stackOffsetsData[VARIABLE_DATA_SIZE];
	//unsigned int* stackOffsets = stackOffsetsData;

	while (TRUE)
	{
		printf("%s\n", instructionTypeStrings[i->type]);
		switch (i->type)
		{
		case IT_ASSIGN_VAR_TO_VAR:
			{

				struct Variable* dest = PSUB_INSTRUCTION(i, struct AssignVarToVarIns)->dest;
				struct Variable* src  = PSUB_INSTRUCTION(i, struct AssignVarToVarIns)->src;

				IF_VAR_NOT_EXIST_CREATE(dest);

				// Generate
				// 1. MOV [32] REG, [32] var1
				// 2. MOV [32] var0, [32] REG

				AddMovDerefEBPWDispToReg(X86I_R_ECX, src->extra);
				AddMovRegToDerefEBPWDisp(dest->extra, X86I_R_ECX);

				ADD_TO_I(struct AssignVarToVarIns);
				break;
			}
		case IT_ASSIGN_CONSTANT_TO_VAR:
			{
				struct Variable* dest = PSUB_INSTRUCTION(i, struct AssignConstantToVarIns)->dest;
				struct Constant* src  = &PSUB_INSTRUCTION(i, struct AssignConstantToVarIns)->src;
				struct Symbol* symbol = dest->symbol;

				//IF_VAR_NOT_EXIST_CREATE(dest->extra);

				if (dest->extra == 0)\
				{\
					stackOffset -= variableTypeSizes[dest->type];\
					dest->extra = stackOffset;\
					//printf("EBP[%d] = %s\n", stackOffset, dest->symbol->name);
				}

				// Generate
				// 1. MOV [32] var, CONSTANT
				AddMovConstToDerefEBPWDisp(dest->extra, src);

				ADD_TO_I(struct AssignConstantToVarIns);
				break;
			}
		case IT_ADD_VAR_TO_VAR:
			{
				struct Variable* dest = PSUB_INSTRUCTION(i, struct AddVarToVarIns)->dest;
				struct Variable* src  = PSUB_INSTRUCTION(i, struct AddVarToVarIns)->src;

				// Generate
				// 1. MOV [32] REG, [32] var1
				// 2. ADD [32] var0, [32] REG

				AddMovDerefEBPWDispToReg(X86I_R_ECX, src->extra);
				AddAddRegToDerefEBPWDisp(dest->extra, X86I_R_ECX);

				ADD_TO_I(struct AddVarToVarIns);
				break;
			}
		case IT_ADD_CONSTANT_TO_VAR:
			{
				struct Variable* dest = PSUB_INSTRUCTION(i, struct AddConstantToVarIns)->dest;
				struct Constant* src  = &PSUB_INSTRUCTION(i, struct AddConstantToVarIns)->src;

				// Generate
				// 1. ADD var0, immm

				AddAddConstToDerefEBPWDisp(dest->extra, src);

				ADD_TO_I(struct AddConstantToVarIns);
				break;
			}
		case IT_SUB_VAR_FROM_VAR:
			{
				struct Variable* dest = PSUB_INSTRUCTION(i, struct AddVarToVarIns)->dest;
				struct Variable* src  = PSUB_INSTRUCTION(i, struct AddVarToVarIns)->src;

				// Generate
				// 1. MOV [32] REG, [32] var1
				// 2. ADD [32] var0, [32] REG

				AddMovDerefEBPWDispToReg(X86I_R_ECX, src->extra);
				AddSubRegFromDerefEBPWDisp(dest->extra, X86I_R_ECX);

				ADD_TO_I(struct AddVarToVarIns);
				break;
			}
		case IT_SUB_CONSTANT_FROM_VAR:
			{
				struct Variable* dest = PSUB_INSTRUCTION(i, struct SubConstantFromVarIns)->dest;
				struct Constant* src  = &PSUB_INSTRUCTION(i, struct SubConstantFromVarIns)->src;

				// Generate
				// 1. SUB var0, immm

				AddSubConstFromDerefEBPWDisp(dest->extra, src);
				ADD_TO_I(struct SubConstantFromVarIns);
				break;
			}
		case IT_MUL_VAR_BY_VAR:
			{
				struct Variable* dest = PSUB_INSTRUCTION(i, struct AddVarToVarIns)->dest;
				struct Variable* src  = PSUB_INSTRUCTION(i, struct AddVarToVarIns)->src;

				// IMUL reg32,r/m32              ; o32 0F AF /r         [386]
				// Generate
				// mov    ECX, var0
				// imul   ECX, var1
				// mov    var0, ECX

				AddMovDerefEBPWDispToReg(X86I_R_ECX, dest->extra);
				
				{
					struct IMulRegisterByDerefRegister* op = ADD_X86IOP(struct IMulRegisterByDerefRegister, X86I_OT_IMUL_REG_BY_DEREFREG);
					op->dest = X86I_R_ECX;
					op->src.reg = X86I_R_EBP;
					op->src.disp = src->extra;
				}

				ADD_TO_I(struct AddVarToVarIns);
				break;
			}
		case IT_MUL_VAR_BY_CONSTANT:
			{
				struct Variable* dest = PSUB_INSTRUCTION(i, struct MulVarByConstantIns)->dest;
				struct Constant* src  = &PSUB_INSTRUCTION(i, struct MulVarByConstantIns)->src;

				// Generate
				// IMUL ECX, var, imm
				// MOV var, ECX

				struct IMulDerefRegByConstantToReg* op = ADD_X86IOP(struct IMulDerefRegByConstantToReg, X86I_OT_IMUL_DEREFREG_BY_CONST_TO_REG);
				op->dest = X86I_R_ECX;
				op->factor0.reg = X86I_R_EBP;
				op->factor0.disp = dest->extra;
				op->factor1 = *src;

				AddMovRegToDerefEBPWDisp(dest->extra, X86I_R_ECX);

				ADD_TO_I(struct MulVarByConstantIns);
				break;
			}
		case IT_DIV_VAR_BY_VAR:
			{
				struct Variable* dest = PSUB_INSTRUCTION(i, struct AddVarToVarIns)->dest;
				struct Variable* src  = PSUB_INSTRUCTION(i, struct AddVarToVarIns)->src;

				// Generate
				// MOV ECX, EAX
				// MOV EAX, var0
				// DIV var1
				// MOV var0, EAX
				// MOV EAX, ECX

				// Save EAX
				AddMovRegToReg(X86I_R_ECX, X86I_R_EAX);
				
				AddMovConstToReg(X86I_R_EDX, &zeroConstant);
				AddMovDerefEBPWDispToReg(X86I_R_EAX, dest->extra);

				{
					struct IDivDerefRegToEAXAndEDX * op = ADD_X86IOP(struct IDivDerefRegToEAXAndEDX, X86I_OT_IDIV_DEREFREG_TO_EAX_AND_EDX);
					op->divisor.reg = X86I_R_EBP;
					op->divisor.disp = dest->extra;
				}

				AddMovRegToDerefEBPWDisp(dest->extra, X86I_R_EAX);

				// Restore EAX
				AddMovRegToReg(X86I_R_EAX, X86I_R_ECX);


				ADD_TO_I(struct AddVarToVarIns);
				break;
			}
		case IT_DIV_VAR_BY_CONSTANT:
			{
				struct Variable* dest = PSUB_INSTRUCTION(i, struct DivVarByConstantIns)->dest;
				struct Constant* src  = &PSUB_INSTRUCTION(i, struct DivVarByConstantIns)->src;

				// Generate
				// MOV ECX, EAX
				// MOV EAX, var
				// MOV EBX, imm
				// DIV EBX
				// MOV var, EAX
				// MOV EAX, ECX

				
				// Save EAX
				AddMovRegToReg(X86I_R_ECX, X86I_R_EAX);

				AddMovConstToReg(X86I_R_EDX, &zeroConstant);
				//AddMovDerefEBPWDispToReg(X86I_R_DX, dest->extra + variableTypeSizes[dest->type]/2);
				AddMovDerefEBPWDispToReg(X86I_R_EAX, dest->extra);
				AddMovConstToReg(X86I_R_EBX, src);
				{
					struct IDivRegToEAXAndEDX * op = ADD_X86IOP(struct IDivRegToEAXAndEDX, X86I_OT_IDIV_REG_TO_EAX_AND_EDX);
					op->divisor = X86I_R_EBX;
				}
				AddMovRegToDerefEBPWDisp(dest->extra, X86I_R_EAX);
				AddMovRegToReg(X86I_R_EAX, X86I_R_ECX);

				ADD_TO_I(struct DivVarByConstantIns);
				break;
			}
		case IT_JMP:
			{
				break;
			}
		case IT_RETURN:
			{
				ADD_X86IRET();
				goto End;
				break;
			}
		}
	}

End:
	return;
}
